/**
 * ConverterFactory
 *
 * @author Chris Pratt
 *
 * 12/15/2011
 */
package com.anodyzed.onyx.type;

import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class ConverterFactory {

  /**
   * ConverterKey
   */
  private static class ConverterKey {
    private Class<?> type1;
    private Class<?> type2;
    
    /**
     * Constructor
     *
     * @param t1 One of the Conversion Types 
     * @param t2 The other Conversion Type 
     */
    public ConverterKey (Class<?> t1,Class<?> t2) {
      this.type1 = t1;
      this.type2 = t2;
    } //ConverterKey

    /**
     * Computer a Hash Bucket Location that allows the types to be in any order
     *
     * @return Hash Index 
     */
    @Override
    public int hashCode () {
      return type1.hashCode() + type2.hashCode();
    } //hashCode

    /**
     * Consider the Converter Key equal if the types are the same, regardless of their order
     *
     * @param o The ConverterKey in question
     * @return true if equivalent
     */
    @Override
    public boolean equals (Object o) {
      return (o instanceof ConverterKey) && ((((ConverterKey)o).type1.isAssignableFrom(type1) && ((ConverterKey)o).type2.isAssignableFrom(type2)) || (((ConverterKey)o).type1.isAssignableFrom(type2) && ((ConverterKey)o).type2.isAssignableFrom(type1)));
    } //equals
  
  } //*ConverterKey

  private static ConverterFactory defaultFactory = null;
  protected static Map<ConverterKey,Converter> defaults;
  static {
    defaults = new HashMap<>();
    Converter converter = new StringBooleanConverter();
    defaults.put(new ConverterKey(CharSequence.class,Boolean.class),converter);
    defaults.put(new ConverterKey(CharSequence.class,Boolean.TYPE),converter);
    converter = new StringByteConverter();
    defaults.put(new ConverterKey(CharSequence.class,Byte.class),converter);
    defaults.put(new ConverterKey(CharSequence.class,Byte.TYPE),converter);
    converter = new StringCharConverter();
    defaults.put(new ConverterKey(CharSequence.class,Character.class),converter);
    defaults.put(new ConverterKey(CharSequence.class,Character.TYPE),converter);
    converter = new StringDoubleConverter();
    defaults.put(new ConverterKey(CharSequence.class,Double.class),converter);
    defaults.put(new ConverterKey(CharSequence.class,Double.TYPE),converter);
    converter = new StringFloatConverter();
    defaults.put(new ConverterKey(CharSequence.class,Float.class),converter);
    defaults.put(new ConverterKey(CharSequence.class,Float.TYPE),converter);
    converter = new StringIntConverter();
    defaults.put(new ConverterKey(CharSequence.class,Integer.class),converter);
    defaults.put(new ConverterKey(CharSequence.class,Integer.TYPE),converter);
    converter = new StringLongConverter();
    defaults.put(new ConverterKey(CharSequence.class,Long.class),converter);
    defaults.put(new ConverterKey(CharSequence.class,Long.TYPE),converter);
    converter = new StringShortConverter();
    defaults.put(new ConverterKey(CharSequence.class,Short.class),converter);
    defaults.put(new ConverterKey(CharSequence.class,Short.TYPE),converter);
    defaults.put(new ConverterKey(CharSequence.class,Enum.class),new StringEnumConverter());
    defaults.put(new ConverterKey(CharSequence.class,Date.class),new StringDateConverter());
    defaults.put(new ConverterKey(Calendar.class,Date.class),new DateCalendarConverter());
    converter = new BigDecimalDoubleConverter();
    defaults.put(new ConverterKey(BigDecimal.class,Double.class),converter);
    defaults.put(new ConverterKey(BigDecimal.class,Double.TYPE),converter);
  }

  protected Map<ConverterKey,Converter> converters;

  /**
   * Constructor
   */
  public ConverterFactory () {
    converters = new HashMap<>(defaults);
  } //ConverterFactory

  /**
   * Add a new Converter to this Factory instance
   *
   * @param t1 One of the conversion types 
   * @param t2 The other conversion type 
   * @param converter The converter implementation
   */
  public void addConverter (Class<?> t1,Class<?> t2,Converter converter) {
    converters.put(new ConverterKey(t1,t2),converter);
  } //addConverter

  /**
   * Get a Converter for the specified types
   *
   * @param t1 One of the conversion types 
   * @param t2 The other conversion types 
   * @return The converter implementation
   */
  public Converter getConverter (Class<?> t1,Class<?> t2) {
    ConverterKey key = new ConverterKey(t1,t2);
    Converter converter = converters.get(key);
    if(converter == null) {
      for(Map.Entry<ConverterKey,Converter> entry : converters.entrySet()) {
        if(key.equals(entry.getKey())) {
          return entry.getValue();
        }
      }
    }
    return converter;
  } //getConverter

  /**
   * Add a default Converter to be used when converting between the specified types by any Factories created after this point
   *
   * @param t1 One of the conversion types 
   * @param t2 The other conversion type 
   * @param converter The default converter implementation 
   */
  public static void addDefaultConverter (Class<?> t1,Class<?> t2,Converter converter) {
    defaults.put(new ConverterKey(t1,t2),converter);
  } //addDefaultConverter

  /**
   * Retrieve the default converter factory. 
   *  
   * NB: The only way to add converters to the returned instance is to add 
   * additional default converters
   *
   * @return The Default Converter Factory 
   */
  public static ConverterFactory getDefaultFactory () {
    if(defaultFactory == null) {
      defaultFactory = new ConverterFactory() {
        {
          converters = defaults;
        }

        /**
         * Add a new Converter to this Factory instance
         *
         * @param t1 One of the conversion types
         * @param t2 The other conversion type
         * @param converter The converter implementation
         */
        @Override
        public void addConverter(Class<? > t1, Class<? > t2, Converter converter) {
          throw new UnsupportedOperationException("The Default Converter does not allow Converters to be added at runtime");
        } //addConverter
      }; //*ConverterFactory
    }
    return defaultFactory;
  } //getDefaultFactory

} //*ConverterFactory
